﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary.DataMining.ClusterAnalysis
{
    /// <summary>
    /// This class represents a neuron.
    /// </summary>
    [Serializable]
    public class SelfOrganisingMapNeuron : ISelfOrganisingMapNeuron
    {
        /// <summary>
        /// The weights of the neuron.
        /// </summary>
        private double[] weights;

        /// <summary>
        /// The x position of the neuron.
        /// </summary>
        private int x;

        /// <summary>
        /// The y position of the neuron.
        /// </summary>
        private int y;

        /// <summary>
        /// The lenght of the neuron.
        /// </summary>
        private readonly int length;

        /// <summary>
        /// Unspecified helper field.
        /// </summary>
        private readonly double nf;

        /// <summary>
        /// Initializes a new instance of the <see cref="SelfOrganisingMapNeuron"/> class.
        /// </summary>
        /// <param name="x">The x position of the neuron.</param>
        /// <param name="y">The y position of the neuron.</param>
        /// <param name="length">The lenght of the neuron.</param>
        public SelfOrganisingMapNeuron(int x, int y, int length)
        {
            this.x = x;
            this.y = y;
            this.length = length;

            nf = 1000 / Math.Log(length);
        }

        /// <summary>
        /// Gets or sets the x position of the neuron.
        /// </summary>
        /// <value>The x position of the neuron.</value>
        public int X
        {
            get { return x; }
            set { x = value; }
        }

        /// <summary>
        /// Gets or sets the y position of the neuron.
        /// </summary>
        /// <value>The y position of the neuron.</value>
        public int Y
        {
            get { return y; }
            set { y = value; }
        }

        /// <summary>
        /// Gets or sets the weights of the neuron.
        /// </summary>
        /// <value>The weights of the neuron.</value>
        public double[] Weights
        {
            get { return weights; }
            set { weights = value; }
        }

        /// <summary>
        /// Calculates the Gauss distance.
        /// </summary>
        /// <param name="win">The specified neuron.</param>
        /// <param name="it">The current iteration.</param>
        /// <returns>The computed distance.</returns>
        private double Gauss(SelfOrganisingMapNeuron win, int it)
        {
            double distance = Math.Sqrt(Math.Pow(win.X - X, 2) + Math.Pow(win.Y - Y, 2));

            return Math.Exp(-Math.Pow(distance, 2) / (Math.Pow(Strength(it), 2)));
        }

        /// <summary>
        /// Computes the learning rate.
        /// </summary>
        /// <param name="it">The current iteration.</param>
        /// <returns>The computed learning rate.</returns>
        private static double LearningRate(int it)
        {
            return Math.Exp(-it / 1000.0) * 0.1;
        }

        /// <summary>
        /// Calculates the strength.
        /// </summary>
        /// <param name="it">The current iteration.</param>
        /// <returns>The strength.</returns>
        private double Strength(int it)
        {
            return Math.Exp(-it / nf) * length;
        }

        /// <summary>
        /// Updates the weights.
        /// </summary>
        /// <param name="pattern">The pattern.</param>
        /// <param name="winner">The winner.</param>
        /// <param name="it">The current iteration.</param>
        /// <returns>The average of the weights.</returns>
        public double UpdateWeights(double[] pattern, SelfOrganisingMapNeuron winner, int it)
        {
            if (pattern == (double[]) null)
            {
                throw new ArgumentNullException("pattern");
            }

            double sum = 0;

            for (int i = 0; i < Weights.Length; i++)
            {
                double delta = LearningRate(it) * Gauss(winner, it) * (pattern[i] - Weights[i]);

                this.weights[i] += delta;
                sum += delta;
            }

            return sum / Weights.Length;
        }
    }
}